package sudoku;



import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.Observable;

import controleur.IObservable;
import controleur.IObserver;


public class SudokuGridMaker implements IObservable
{
	private static String SUDOKU_FILE = "sudoku.txt";
	private static String LOG_FILE = "sudokuLog.log";
	int [][] sudokuGrid = new int[9][9];
	private long startTime;
	private long stopTime;
	
	// Observer = controller.
	private IObserver observer = null;
	private FileWriter logWriter;
	
	
	public SudokuGridMaker() throws IOException
	{
		this.logWriter = new FileWriter(LOG_FILE, true);
		this.startTime  = 0;
		this.stopTime  = 0;
	}
	
	public int [][] makeSudokuGrid()
	{	
		// Initialize grid.
		for (int i = 0; i < 9; i++)
			for (int j = 0; j < 9; j++)
				sudokuGrid[i][j] = 0;
		
		// Open file.
		try
		{
			FileReader fr = new FileReader(SUDOKU_FILE);
			BufferedReader bf = new BufferedReader(fr);
			String line = "";
			
			//# LOG
			//SLogger.logMessage("Reading sudoku file...", this.logWriter);
			int i = 0;
			int j = 0;
			while ((line = bf.readLine()) != null)
			{
				//System.out.println(line);
				//# LOG
				//SLogger.logMessage("Line read: " + line, this.logWriter);
				
				if (line.length() != 9)
					throw new BadSudokuFileException();
				
				for (j = 0; j < line.length(); j++)
				{
					sudokuGrid[i][j] = Integer.parseInt("" + line.toCharArray()[j]);
				}
				i++;
			}
			
			//# LOG
			//SLogger.logMessage("Reading sudoku file terminated!", this.logWriter);
			
			fr.close();
			bf.close();
		}
		catch (FileNotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		} catch (BadSudokuFileException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		
		return sudokuGrid;
	}
	
	/**
	 * Checks the entire grid's validity by checking the position of all numbers.
	 * @return True or false.
	 */
	public boolean isGridValid()
	{
		//# LOG
		//SLogger.logMessage("Checking grid validity...", this.logWriter);
		for (int i = 0; i < 9; i++)
		{
			for (int j = 0; j < 9; j++)
			{
				if (sudokuGrid[i][j] != 0)
				{
					if (!isPositionValid(i, j, sudokuGrid[i][j], true))
					{
						//# LOG
						//SLogger.logMessage(sudokuGrid[i][j] + " at (" + i + ", " + j + ") is not valid", this.logWriter);
						//SLogger.logMessage("The grid is not valid!", this.logWriter);
						return false;
					}
					//# LOG
					//SLogger.logMessage(sudokuGrid[i][j] + " at (" + i + ", " + j + ") is valid", this.logWriter);
				}
			}
		}
		//# LOG
		//SLogger.logMessage("The initial grid is valid!", this.logWriter);
		return true;
	}
	
	/**
	 * Checks if the position (posX, posY) of the given number is valid.
	 * @param posX Abscissa
	 * @param posY Ordinate
	 * @param number Number at position (posX, posY)
	 * @return True or false.
	 */
	public boolean isPositionValid(int posX, int posY, int number, boolean firstCheck)
	{
		return (isInRow(number, posX, posY, firstCheck) && isInColumn(number, posX, posY, firstCheck) && isInRegion(number, posX, posY, firstCheck));
	}
	
	/**
	 * Checks if the given number appears more than once in its row.
	 * number is not in sudokuGrid[x][0..8]
	 * @param sudokuGrid
	 * @param number
	 * @param posX
	 * @param posY
	 * @return
	 */
	public boolean isInRow(int number, int posX, int posY, boolean firstCheck)
	{
		for (int i = 0; i < 9; i++)
		{
			if(firstCheck)
			{
				if ((i != posY) && (sudokuGrid[posX][i] == number))
				{
					//# LOG
					//SLogger.logMessage(number + " is found more than once in row #" + posX);
					return false;
				}
			}
			else if (sudokuGrid[posX][i] == number)
			{
				//# LOG
				//SLogger.logMessage(number + " is already in row #" + posX);
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Checks if the given number appears more than once in its column.
	 * number is not in sudokuGrid[0..8][y]
	 * @param sudokuGrid
	 * @param number
	 * @param posX
	 * @param posY
	 * @return
	 */
	public boolean isInColumn(int number, int posX, int posY, boolean firstCheck)
	{
		for (int i = 0; i < 9; i++)
		{
			if (firstCheck)
			{
				if ((i != posX) && (sudokuGrid[i][posY] == number))
				{
					//# LOG
					//SLogger.logMessage(number + " is found more than once in column #" + posY);
					return false;
				}
			}
			else if (sudokuGrid[i][posY] == number)
			{
				//# LOG
				//SLogger.logMessage(number + " is already in column #" + posY);
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Checks if the given number appears more than once in its region.
	 * number is not in sudokuGrid[(x/3 * 3)..(x/3 * 3 + 3)][(y/3 * 3)..(y/3 * 3 + 3)]
	 * Divisions are floored.
	 * @param sudokuGrid
	 * @param posX
	 * @return
	 */
	public boolean isInRegion(int number, int posX, int posY, boolean firstCheck)
	{
		// Calculate the region's boundaries.
		int absStart = (int)(Math.floor((double)posX/3) * 3);
		int absStop = absStart + 3;
		int ordStart = (int)(Math.floor((double)posY/3) * 3);
		int ordStop = ordStart + 3;
		
		for (int i = absStart; i < absStop; i++)
		{
			for (int j = ordStart; j < ordStop; j++)
			{
				if (firstCheck)
				{
					if ((i != posX && j != posY) && sudokuGrid[i][j] == number)
					{
						//# LOG
						//SLogger.logMessage(number + " is found more than once in the region grid[" + absStart + ".." + absStop + "][" + ordStart + ".." + ordStop + "]", this.logWriter);
						return false;
					}
				}
				else if (sudokuGrid[i][j] == number)
				{
					//# LOG
					//SLogger.logMessage(number + " is already in the region grid[" + absStart + ".." + absStop + "][" + ordStart + ".." + ordStop + "]", this.logWriter);
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * Solve the sudoku grid with backtracking algorithm.
	 * @param position Position of the cell being treated. 
	 *        We start with the first one (0, 0).
	 * @return True (Solution) or false (no solution).
	 * @throws InterruptedException 
	 */
	public boolean solveGrid(int position) throws InterruptedException
	{
		// Solution found!
		if (position == (9 * 9))
		{
			this.stopTimer();
			this.notifyObservers(this, "Solved");
			return true;
		}
		
		
		int posX = position / 9;
		int posY = position % 9;
		
		if (sudokuGrid[posX][posY] != 0)
			return solveGrid(position + 1);
		
		for (int value = 1; value < 10; value++)
		{
			if (isPositionValid(posX, posY, value, false))
			{
				sudokuGrid[posX][posY] = value;
				
				// Notify the observer.
				//this.notifyObservers(this, "Update");
				//Thread.sleep(10);
				
				if (solveGrid(position + 1))
					return true;
			}
		}
		sudokuGrid[posX][posY] = 0;
		
		// Notify the observer.
		//this.notifyObservers(this, "Update");
		//Thread.sleep(1000);
		
		// No solution found!
		if (position == 0)
		{
			this.stopTimer();
			this.notifyObservers(this, "Unsolved");
		}
		
		return false;
	}
	
	public int [][] getSudokuGrid()
	{
		return this.sudokuGrid;
	}
	
	@Override
	public void removeObserver(IObserver observer) {
		// TODO Auto-generated method stub
		
	}
	
	/**
	 * Ajoute un observer
	 * @param observer
	 */
	public void addObserver(IObserver obs)
	{
		this.observer = obs;
	}

	/**
	 * Notifie tous les observateurs
	 * @param observer
	 */
	public void notifyObservers(IObservable observed, Object obj)
	{
		if (observer != null)
			this.observer.update(observed, obj);
	}
	
	public void setSudokuFile(String file)
	{
		SUDOKU_FILE = file;
	}
	
	public long getStartTime()
	{
		return this.startTime;
	}
	
	public long getStopTime()
	{
		return this.stopTime;
	}
	
	/**
	 * Demarre le timer.
	 */
	public void startTimer()
	{
		this.startTime =  System.nanoTime();
	}
	
	/**
	 * Demarre le timer.
	 */
	public void stopTimer()
	{
		this.stopTime =  System.nanoTime();
	}
	
	/**
	 * Retourne le temps ecoule en nanosecondes.
	 * @return long
	 */
	public long getElapsedTime()
	{
		return this.stopTime - this.startTime;
	}
	
	public static void main(String [] args) throws IOException, InterruptedException
	{
		/*SudokuGridMaker s = new SudokuGridMaker(); 
		int [][] sgrid = s.makeSudokuGrid();
		
		for (int i = 0; i < 9; i++)
		{
			for (int j = 0; j < 9; j++)
			{
				System.out.print(sgrid[i][j]);
			}
			System.out.println();
		}
		
		System.out.println("Grille est valide = " + s.isGridValid());
		System.out.println("Solving grid...");
		
		Thread t = new Thread();
		
		Calendar c = Calendar.getInstance();
		
		long time = System.nanoTime();
		
		s.startTimer();
		if (!s.isGridValid())
    	{
    		s.stopTimer();
    	}
		else if (s.solveGrid(0))
		{
			System.out.println("Grid is solvable in " + (s.getElapsedTime() / 1000000000.0) + " nanoseconds.");
			System.out.println("starttime = " + s.getStartTime());
			System.out.println("stoptime = " + s.getStopTime());
			for (int i = 0; i < 9; i++)
			{
				for (int j = 0; j < 9; j++)
				{
					System.out.print(s.getSudokuGrid()[i][j]);
				}
				System.out.println();
			}
		}
		else
		{
			s.stopTimer();
			System.out.println("Grid is not solvable!\n Resolved in " + (s.getElapsedTime() / 1000000000) + " seconds.");
		}*/
	}
}
	
